package com.tencent.welink.iot;

import com.alibaba.fastjson.JSONObject;
import com.leaf.lowcode.utils.RegularInstance;
import com.tencent.welink.config.BussinessConfig;
import com.tencent.welink.sm.SM2Utils;
import com.tencent.welink.sm.Util;
import com.tencent.welink.util.CrontabUtil;
import com.tencent.welink.util.MD5Util;
import com.tencent.welink.util.RandomUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.charset.Charset;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created by junchihe on 2020/6/5.
 */
public class IOTLogin {
    private static final Logger log = LogManager.getLogger(IOTLogin.class);
    private static int THREADS_FOR_MESSAGE;
    private static  int THREADS;

    static {
        THREADS_FOR_MESSAGE = Runtime.getRuntime().availableProcessors();
        THREADS = THREADS_FOR_MESSAGE*10;
        //VIDEO_PUSH_THREADS = THREADS_FOR_MESSAGE*2;
        if (THREADS>=40){
            THREADS = THREADS-5;
        }
       /* if (VIDEO_PUSH_THREADS>40){
            VIDEO_PUSH_THREADS = VIDEO_PUSH_THREADS-5;
        }*/
        log.info("Thread num is "+THREADS+"...........message thread pool is "+ THREADS_FOR_MESSAGE);
    }
    public static ExecutorService threadPool = Executors.newFixedThreadPool(THREADS);

    public static ConcurrentHashMap<String,Boolean> DO_CONNECTED = new ConcurrentHashMap<>();

    private static byte[] encode(Integer cmd,byte[] msgByte) {

//        byte[] msgByte = msg.getBytes();
        int len = msgByte.length;

        byte[] resB = new byte[2+4+msgByte.length];

        resB[0] = (byte) (cmd >> 8 & 0xff);
        resB[1] = (byte) (cmd & 0xff);
        resB[2] = (byte) (len >> 24 & 0xff);
        resB[3] = (byte) (len >> 16 & 0xff);
        resB[4] = (byte) (len >> 8 & 0xff);
        resB[5] = (byte) (len & 0xff);

        System.arraycopy(msgByte, 0, resB, 6, len);

        return resB;

    }


    public static RegisterData register() {

        String sign = getLicence(BussinessConfig.pid,BussinessConfig.sn,null);

        JSONObject json = new JSONObject();
        //for old version
        //json.put("pid", BussinessConfig.pid);
        json.put("sn", BussinessConfig.sn);
        json.put("lic", sign);
        json.put("ts", System.currentTimeMillis());

        byte[] encrypt = SM2Utils.encrypt(Util.hexToByte(BussinessConfig.SERVER_PUBLIC_KEY), json.toJSONString().getBytes());

        JSONObject reqJson = new JSONObject();
        reqJson.put("data", Util.byteToHex(encrypt));
        reqJson.put("mac", MD5Util.getMd5(Util.byteToHex(encrypt)));
        //for new version
        reqJson.put("pid",BussinessConfig.pid);
        reqJson.put("v",2);

        String jsonStr = reqJson.toJSONString();
        //old=0xA,new=0x000D
        String ret = socket(encode(0xD, jsonStr.getBytes(Charset.forName("utf-8"))),BussinessConfig.ip,BussinessConfig.port);

        log.info("response:"+ret);

        JSONObject retJson = JSONObject.parseObject(ret);

        String data = retJson.getString("data");
        checkMd5(data,retJson.getString("mac"));
        checkMsgCode(retJson.getInteger("code"),retJson.getString("msg"));
        data = new String(SM2Utils.decrypt(Util.hexToByte(BussinessConfig.PRODUCT_PRIVATE_KEY), Util.hexToByte(data)),Charset.forName("utf-8"));
        log.info("decrypt data:"+ data);
        return JSONObject.parseObject(data,RegisterData.class);
    }


    public static int challenge(String ip,int port) {

        byte[] msg = new byte[1];
        msg[0] = 1;

        String ret = socket(encode(0xB, msg),ip,port);

        log.info("response:"+ret);

        JSONObject retJson = JSONObject.parseObject(ret);

        int ran = retJson.getInteger("data");
        log.info(ret);

        return ran;

    }


    private static String getLicence (String pid,String sn,Integer ran) {

        String resource = "";

        if(ran==null) {

            resource = pid+sn;

        }else {

            resource = pid+sn+ran;

        }

        byte[] sign = SM2Utils.sign(BussinessConfig.pid.getBytes(Charset.forName("utf-8")),Util.hexToByte(BussinessConfig.PRODUCT_PRIVATE_KEY), resource.getBytes(Charset.forName("utf-8")));

        String lic = Util.byteToHex(sign);

        return lic;

    }


    public static LoginData login(final RegisterData registerData,final LoginData loginData) {

        log.info("registerData {}",JSONObject.toJSON(registerData));
        String address = registerData.projAccessAddr.get(0);
        String[] arrs = StringUtils.split(address,':');
        String ip = arrs[0];
        int  port  = Integer.valueOf(StringUtils.strip(arrs[1]));
        //挑战应答获取随机数
        int challengeRan = challenge(ip,port);

        //签名pid，sn，随机数
        String lic = getLicence(BussinessConfig.pid,BussinessConfig.sn,challengeRan);


        log.info("licence:"+lic);

        String sm4IV = RandomUtil.getRandomStr(16, null);

        log.info("sm4 IV:"+sm4IV);

        JSONObject json = new JSONObject();
        json.put("din", registerData.din);
        json.put("wId",registerData.wId);
        json.put("challenge_ran", challengeRan);
        json.put("ran", sm4IV);
        json.put("ts", System.currentTimeMillis());
        json.put("lic", lic);

        byte[] encrypt = SM2Utils.encrypt(Util.hexToByte(BussinessConfig.SERVER_PUBLIC_KEY), json.toJSONString().getBytes(Charset.forName("utf-8")));

        JSONObject reqJson = new JSONObject();
        reqJson.put("data", Util.byteToHex(encrypt));
        reqJson.put("mac", MD5Util.getMd5(Util.byteToHex(encrypt)));
        //for new version
        reqJson.put("pid",BussinessConfig.pid);
        json.put("din", registerData.din);
        json.put("wId",registerData.wId);
        reqJson.put("v",2);
        //old =0xC new =0x000E
        String ret = socket(encode(0xE, reqJson.toJSONString().getBytes(Charset.forName("utf-8"))),ip,port);

        log.info("response:"+ret);

        JSONObject retJson = JSONObject.parseObject(ret);

        String dataJson = retJson.getString("data");
        checkMd5(dataJson,retJson.getString("mac"));
        checkMsgCode(retJson.getInteger("code"),retJson.getString("msg"));
        dataJson = new String(SM2Utils.decrypt(Util.hexToByte(BussinessConfig.PRODUCT_PRIVATE_KEY), Util.hexToByte(dataJson)),Charset.forName("utf-8"));
        log.info("data:"+dataJson);

        LoginData loginData01 =  JSONObject.parseObject(dataJson,LoginData.class);
        loginData01.sm4IV = sm4IV;
        loginData.set(loginData01);
        return loginData;
    }

    public static boolean checkMsgCode(Integer code,String  msg){
        if(code ==null || code != 0) {
            log.info("check msg code is false,msg is {}",msg);
            return false;
        }
        log.info("check msg code is true,msg is {}",msg);
        return true;
    }

    public static boolean checkMd5(String data,String mac){
        if(mac == null || data == null) {
            log.info("mac or data is null");
            return false;
        }
        String md5Str = MD5Util.getMd5(data);
        if(mac.equals(md5Str)){
            log.info("check mac equal data is true");
            return true;
        }
        log.info("check mac {} equal data md5 {} is false",mac,md5Str);
        return false;
    }


    public static String socket(byte[] data,String ip,int port) {

        Socket socket = null;
        try {
            socket = new Socket();
            socket.setSoTimeout( 5000);
            InetSocketAddress addrSkt = new InetSocketAddress(ip,port);
            socket.connect(addrSkt, 2000);

            InputStream in = socket.getInputStream();
            OutputStream out = socket.getOutputStream();

            DataOutputStream dataOut = new DataOutputStream(out);
            DataInputStream dataIn = new DataInputStream(in);

            dataOut.write(data);
            dataOut.flush();

            int intVer = dataIn.readShort();
            int jsonLen = dataIn.readInt();
            log.debug("ver is {} ,reserver is {}", intVer, jsonLen);
            if (jsonLen > 10000 || jsonLen <= 0) {
                // 大于10k错误
                log.error("the length is wrong:" + jsonLen);
                return null;
                // throw new Exception("the length is wrong:" + jsonLen);
            }
            byte[] strBytes = new byte[jsonLen];
            int read = dataIn.read(strBytes);
            Charset charSetUtf8 = Charset.forName("utf8");
            String strJson = new String(strBytes, charSetUtf8);
//            logger.debug("ret str is " + strJson);

            String ret = new String(strBytes,Charset.forName("utf-8"));

            return ret;

        } catch (Exception ex) {
            log.error("send inner err:", ex);
            return null;
        } finally {
            try {
                socket.close();
            } catch (Exception exp) {
                log.warn("close socket fail:", exp);
            }
        }
    }

    /**
     * 执行登录
     * @param registerData1
     * @param loginData
     */
    public static LoginData doLogin(final RegisterData registerData1,final LoginData loginData){
        LoginData login = login(registerData1, loginData);

        log.info("loginD    ata :{}",JSONObject.toJSON(loginData));
        return login;
    }

    /**
     * 主函数
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
        new BussinessConfig();
        RegisterData registerData ;
        while(true) {
            registerData = register();
            if ("0".equals(registerData.projId)) {
                Thread.sleep(3*60*1000L);
                log.info("please import gateway imformation to welink project management platform!");
                continue;
            } else {
                break;
            }
        }

        final RegisterData registerData1 = registerData;
        final LoginData loginData = new LoginData();
        LoginData loginData1 = doLogin(registerData1, loginData);
        RegularInstance.token = registerData1.wId;
        RegularInstance.Wid = loginData1.httpToken;
        log.info("login success ============ ");
        RegularInstance.registerData1 = registerData1;
        RegularInstance.loginData = loginData1;

        loginData.lastHttpToken = loginData.httpToken;
        loginData.lastRan = loginData.ran;
        loginData.lastSm4IV = loginData.sm4IV;

        CrontabUtil.addTaskInRate(()->{
            while (true) {
                try {
                    doLogin(registerData1, loginData);
                    log.info("login success ============ ");
                    break;
                }catch (Exception e){
                    log.error(e);
                    try {
                        Thread.sleep(30000);
                    }catch (Exception e1){
                        log.error(e1);
                    }
                    continue;
                }
            }
        },BussinessConfig.mqttDurationHours,true);


        //方式1：MQTT
        final WelinkMqtt welinkMqtt = new WelinkMqtt(registerData.mqttServer,registerData.din,
                registerData.mqttReciveTopic,registerData.mqttPushTopic,registerData.mqttUser,
                registerData.mqttPassword,registerData.mqttClientId,loginData,registerData.wId);
        welinkMqtt.startConnect();

        //方式2：HTTP
//        System.out.println("registerData=="+registerData.);
//        System.out.println("loginData=="+loginData.toString());
//        WelinkHttp welinkHttp = new WelinkHttp(registerData,loginData);
//        welinkHttp.startConnect();
    }
}
